﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Profile.cs" company="Anton Frattaroli">
//   Copyright (c) Anton Frattaroli. All rights reserved.
// </copyright>
// <summary>
//   Defines the Profile class.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace Lidocaine
{
    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Data.SqlClient;
    using System.Globalization;
    using System.Security.Cryptography;
    using System.Text;
    using System.Web;
    using AdamTibi.Web.Security;

    /// <summary>
    /// The Profile class.
    /// </summary>
    public class Profile
    {
        #region Fields
        /// <summary>
        /// Stores the identity field of the database profile entry.
        /// </summary>
        private int userID;

        /// <summary>
        /// Stores the profile's password.
        /// </summary>
        private byte[] password;

        /// <summary>
        /// Stores the username of the user.
        /// </summary>
        private string username;
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the Profile class.
        /// </summary>
        public Profile()
        {
        }

        /// <summary>
        /// Initializes a new instance of the Profile class.
        /// </summary>
        /// <param name="userId">The identity of the database table's row referencing this user.</param>
        public Profile(int userId)
        {
            using (SqlConnection conn = new SqlConnection(Environmental.ConnectionString))
            {
                conn.Open();
                using (SqlCommand cmd = new SqlCommand("Lidocaine.GetProfile", conn))
                {
                    cmd.CommandType = CommandType.StoredProcedure;
                    cmd.Parameters.AddWithValue("@UserID", userId);
                    using (SqlDataReader rdr = cmd.ExecuteReader())
                    {
                        if (rdr.HasRows)
                        {
                            rdr.Read();
                            this.username = rdr["UserName"].ToString();
                            this.password = (byte[])rdr["Password"];
                        }
                    }
                }
            }

            this.userID = userId;
        }
        #endregion

        #region Properties
        /// <summary>
        /// Gets the current user based on the logon cookie.
        /// </summary>
        public static Profile Current
        {
            get
            {
                int userID = 0;
                if (HttpContext.Current.Request.Cookies["Lidocaine"] != null)
                {
                    HttpCookie decoded = HttpSecureCookie.Decode(HttpContext.Current.Request.Cookies["Lidocaine"]);
                    if (Convert.ToDateTime(decoded.Values["Expires"], CultureInfo.InvariantCulture) > DateTime.Now)
                    {
                        userID = Convert.ToInt32(decoded.Values["ID"], CultureInfo.InvariantCulture);
                    }
                }

                return new Profile(userID);
            }
        }

        /// <summary>
        /// Gets the database identity field for this user entry.
        /// </summary>
        public int UserId
        {
            get
            {
                return this.userID;
            }
        }

        /// <summary>
        /// Gets or sets the profile's password.
        /// </summary>
        public byte[] Password
        {
            get
            {
                return this.password;
            }

            set
            {
                this.password = value;
            }
        }

        /// <summary>
        /// Gets or sets the user's username.
        /// </summary>
        public string UserName
        {
            get
            {
                return this.username;
            }

            set
            {
                this.username = value;
            }
        }

        /// <summary>
        /// Gets a value indicating whether or not this user is anonymous.
        /// </summary>
        public bool IsAnonymous
        {
            get
            {
                return this.userID == 0 ? true : false;
            }
        }

        /// <summary>
        /// Gets a list of roles this user is a member of.
        /// </summary>
        public IList<string> Roles
        {
            get
            {
                List<string> roles = new List<string>();
                using (SqlConnection conn = new SqlConnection(Environmental.ConnectionString))
                {
                    conn.Open();
                    using (SqlCommand cmd = new SqlCommand("Lidocaine.GetGroupNamesForUser", conn))
                    {
                        cmd.CommandType = CommandType.StoredProcedure;
                        cmd.Parameters.AddWithValue("@UserID", this.userID);
                        using (SqlDataReader rdr = cmd.ExecuteReader())
                        {
                            if (rdr.HasRows)
                            {
                                while (rdr.Read())
                                {
                                    roles.Add((string)rdr["GroupName"]);
                                }
                            }
                        }
                    }
                }

                return roles.AsReadOnly();
            }
        }
        #endregion

        #region Methods
        /// <summary>
        /// Searches for a user with the given username and domain.
        /// </summary>
        /// <param name="userName">The username of the account.</param>
        /// <param name="domain">The domain of the account (e.g. MSUFGP, ECHTHC, PHMTOX, MHPF, JIT).</param>
        /// <returns>Returns the user associated with the parameter input or null if no user is found.</returns>
        public static Profile GetUserByUserNameAndDomain(string userName, string domain)
        {
            Profile user = null;
            using (SqlConnection conn = new SqlConnection(Environmental.ConnectionString))
            {
                conn.Open();
                using (SqlCommand cmd = new SqlCommand("Lidocaine.GetUserIDByUserName", conn))
                {
                    cmd.CommandType = CommandType.StoredProcedure;
                    cmd.Parameters.AddWithValue("@UserName", userName);
                    using (SqlDataReader rdr = cmd.ExecuteReader())
                    {
                        if (rdr.HasRows)
                        {
                            rdr.Read();
                            user = new Profile(Convert.ToInt32(rdr["ID"], CultureInfo.InvariantCulture));
                        }
                    }
                }
            }

            return user;
        }

        /// <summary>
        /// Selects a list of users from the database.
        /// </summary>
        /// <returns>A read-only list of users.</returns>
        public static IList<Profile> GetUsers()
        {
            List<Profile> users = new List<Profile>();
            using (SqlConnection conn = new SqlConnection(Environmental.ConnectionString))
            {
                conn.Open();
                using (SqlCommand cmd = new SqlCommand("Lidocaine.GetUsers", conn))
                {
                    cmd.CommandType = CommandType.StoredProcedure;
                    using (SqlDataReader rdr = cmd.ExecuteReader())
                    {
                        if (rdr.HasRows)
                        {
                            while (rdr.Read())
                            {
                                users.Add(new Profile(Convert.ToInt32(rdr["ID"], CultureInfo.InvariantCulture)));
                            }
                        }
                    }
                }
            }

            return users.AsReadOnly();
        }

        /// <summary>
        /// Encodes passwords with machine key configuration settings.
        /// </summary>
        /// <param name="password">The password to be encoded.</param>
        /// <returns>A byte array generated from the password.</returns>
        public static byte[] EncodePassword(string password)
        {
            MD5CryptoServiceProvider hasher = new MD5CryptoServiceProvider();
            UTF8Encoding encoder = new UTF8Encoding();
            byte[] hash = hasher.ComputeHash(encoder.GetBytes(HttpContext.Current.Server.HtmlEncode(password)));
            return hash;
        }

        /// <summary>
        /// Authenticates the user using the built-in SQL user accounts.
        /// </summary>
        /// <param name="userName">The username of the account credentials.</param>
        /// <param name="password">The password of the account credentials.</param>
        /// <returns>An instantiated HUser object if successful, null if not authenticated.</returns>
        public static Profile AuthenticateSql(string userName, string password)
        {
            Profile user = null;
            using (SqlConnection conn = new SqlConnection(Environmental.ConnectionString))
            {
                using (SqlCommand cmd = new SqlCommand("Lidocaine.GetUserIDByUserNameAndPassword", conn))
                {
                    cmd.Parameters.AddWithValue("@UserName", userName.ToLower(CultureInfo.CurrentCulture));
                    MD5CryptoServiceProvider hasher = new MD5CryptoServiceProvider();
                    UTF8Encoding encoder = new UTF8Encoding();
                    byte[] hash = hasher.ComputeHash(encoder.GetBytes(HttpContext.Current.Server.HtmlEncode(password)));
                    cmd.Parameters.AddWithValue("@Password", hash);
                    conn.Open();
                    using (SqlDataReader rdr = cmd.ExecuteReader())
                    {
                        if (rdr.HasRows)
                        {
                            rdr.Read();
                            user = new Profile(Convert.ToInt32(rdr["ID"], CultureInfo.InvariantCulture));
                        }
                    }
                }
            }

            return user;
        }

        /// <summary>
        /// Clears the current user's cookie.
        /// </summary>
        public void LogOff()
        {
            HttpCookie decoded = HttpSecureCookie.Decode(HttpContext.Current.Request.Cookies["Lidocaine"]);
            decoded.Values["ID"] = "0";
            decoded.Values["Expires"] = DateTime.Now.AddDays(-1).ToString();
            decoded.Expires = DateTime.Now.AddDays(-1);
            decoded.Domain = Environmental.Domain;
            HttpCookie encoded = HttpSecureCookie.Encode(decoded);
            HttpContext.Current.Response.Cookies.Set(encoded);
        }

        /// <summary>
        /// Sets an authentication cookie identifying this user.
        /// </summary>
        /// <param name="rememberMe">Adjusts the expiration date of the cookie from 1 hour to 14 days if user has chosen to be remembered.</param>
        public void LogOn(bool rememberMe)
        {
            HttpCookie c = new HttpCookie("Lidocaine");
            c.Values["ID"] = this.userID.ToString(CultureInfo.InvariantCulture);
            if (rememberMe)
            {
                c.Values["Expires"] = DateTime.Now.AddDays(14).ToString();
            }
            else
            {
                c.Values["Expires"] = DateTime.Now.AddHours(1).ToString();
            }

            c.Expires = DateTime.Now.AddYears(50);
            c.Domain = Environmental.Domain;
            HttpCookie encoded = HttpSecureCookie.Encode(c);
            HttpContext.Current.Response.Cookies.Add(encoded);
        }

        /// <summary>
        /// Inserts the user into the database.
        /// </summary>
        public virtual void SaveAs()
        {
            using (SqlConnection conn = new SqlConnection(Environmental.ConnectionString))
            {
                conn.Open();
                using (SqlCommand cmd = new SqlCommand("Lidocaine.InsertProfile", conn))
                {
                    cmd.Parameters.AddWithValue("@UserName", this.username);
                    cmd.Parameters.AddWithValue("@Password", this.password);
                    this.userID = Convert.ToInt32(cmd.ExecuteScalar(), CultureInfo.InvariantCulture);
                }
            }
        }

        /// <summary>
        /// Updates the user's database fields.
        /// </summary>
        public virtual void Save()
        {
            using (SqlConnection conn = new SqlConnection(Environmental.ConnectionString))
            {
                conn.Open();
                using (SqlCommand cmd = new SqlCommand("Lidocaine.UpdateProfile", conn))
                {
                    cmd.CommandType = CommandType.StoredProcedure;
                    cmd.Parameters.AddWithValue("@UserID", this.userID);
                    cmd.Parameters.AddWithValue("@Password", this.password);
                    cmd.Parameters.AddWithValue("@UserName", this.username);
                    cmd.ExecuteNonQuery();
                }
            }
        }

        /// <summary>
        /// Deletes the user's database fields.
        /// </summary>
        public virtual void Delete()
        {
            using (SqlConnection conn = new SqlConnection(Environmental.ConnectionString))
            {
                conn.Open();
                using (SqlCommand cmd = new SqlCommand("Lidocaine.DeleteProfile", conn))
                {
                    cmd.CommandType = CommandType.StoredProcedure;
                    cmd.Parameters.AddWithValue("@UserID", this.userID);
                    cmd.ExecuteNonQuery();
                }
            }
        }
        #endregion
    }
}